// Copyright 2001-2007, FreeHEP.
package org.xmind.org.freehep.util.io;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * The ASCII85InputStream encodes binary data as ASCII base-85 encoding. The
 * exact definition of ASCII base-85 encoding can be found in the PostScript
 * Language Reference (3rd ed.) chapter 3.13.3.
 *
 * @author Mark Donszelmann
 * @author Jason Wong
 */
public class ASCII85OutputStream extends FilterOutputStream implements ASCII85,
        FinishableOutputStream {

    private boolean end;

    private int characters;

    private int b[] = new int[4];

    private int bIndex;

    private int c[] = new int[5];

    private String newline = "\n"; //$NON-NLS-1$

    /**
     * Create an ASCII85 Output Stream from given stream
     *
     * @param out
     *            output stream to use
     */
    public ASCII85OutputStream(OutputStream out) {
        super(out);
        characters = MAX_CHARS_PER_LINE;
        end = false;
        bIndex = 0;
        try {
            newline = System.getProperty("line.separator"); //$NON-NLS-1$
        } catch (SecurityException e) {
            // ignored;
        }
    }

    @Override
    public void write(int a) throws IOException {
        b[bIndex] = a & 0x00FF;
        bIndex++;
        if (bIndex >= b.length) {
            writeTuple();
            bIndex = 0;
        }
    }

    public void finish() throws IOException {
        if (!end) {
            end = true;
            if (bIndex > 0) {
                writeTuple();
            }
            writeEOD();
            flush();
            if (out instanceof FinishableOutputStream) {
                ((FinishableOutputStream) out).finish();
            }
        }
    }

    @Override
    public void close() throws IOException {
        finish();
        super.close();
    }

    private void writeTuple() throws IOException {
        // fill the rest
        for (int i = bIndex; i < b.length; i++) {
            b[i] = 0;
        }

        // convert
        long d = ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]) & 0x00000000FFFFFFFFL;

        c[0] = (int) (d / a85p4 + '!');
        d = d % a85p4;
        c[1] = (int) (d / a85p3 + '!');
        d = d % a85p3;
        c[2] = (int) (d / a85p2 + '!');
        d = d % a85p2;
        c[3] = (int) (d / a85p1 + '!');
        c[4] = (int) (d % a85p1 + '!');

        // convert !!!!! to z
        if ((bIndex >= b.length) && (c[0] == '!') && (c[1] == '!')
                && (c[2] == '!') && (c[3] == '!') && (c[4] == '!')) {
            writeChar('z');
        } else {
            for (int i = 0; i < bIndex + 1; i++) {
                writeChar(c[i]);
            }
        }
    }

    // Fix for IO-7
    private void writeEOD() throws IOException {
        if (characters <= 1) {
            characters = MAX_CHARS_PER_LINE;
            writeNewLine();
        }
        characters -= 2;
        super.write('~');
        super.write('>');
    }

    private void writeChar(int b) throws IOException {
        if (characters == 0) {
            characters = MAX_CHARS_PER_LINE;
            writeNewLine();
        }
        characters--;
        super.write(b);
    }

    private void writeNewLine() throws IOException {
        // write a newline
        for (int i = 0; i < newline.length(); i++) {
            super.write(newline.charAt(i));
        }
    }
}
